/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2002, GeoVISTA Center
 * Licensed under Simplified BSD License
 * This library is developed and distributed for free usage,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * You can redistribute and/or modify it under the terms of the
 * Simplified BSD License
 *
 *
 *
 *
 *
 *
*
* This version paint each swatch. Benefit: the Panel can still get AWT events (e.g.: MouseEvent)
*  

* @author: jin Chen
* @date: Jan 15, 2005$
* @version: 1.0
*/
package edu.psu.geovista.app.pcp.toolkits.colorpalette.v1;

import edu.psu.geovista.app.pcp.toolkits.colorpalette.DynamicSwatchPane;
import edu.psu.geovista.common.debug.Debug;
import edu.psu.geovista.datamining.util.ColorSetting;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;


public class DynamicSwatchPaneImpl extends JPanel implements  DynamicSwatchPane {
    private static final boolean DEBUG=false;
    protected int numOfRow;
    protected int numOfCulumn;
    protected Dimension gap=new Dimension(-1,-1);//gap between any 2 swatches
    protected JPanel mainP;
    protected Color[][] colors;
    private Color swatchBorderColor=Color.white ;
    Swatch[][] swatches;//=new ArrayList();
    Set selectedSwatches=new HashSet();
    MouseListener mouseListener;
    MouseMotionListener mouseMotionListener;
    /**
     * Determine how swatches layout: usally GridLayout layout compoenents from top ( 1st row) to bottom. This is the case LAYOUT_TOP_BOTTOM
     * But a Legend usually need to layout from bottom ( represent lower y value) to top. So by setting layouttype=LAYOUT_BOTTOM_TOP,
     * legendControl's color scheme will = interactiveLegend's color scheme
     */
    private int layouttype=LAYOUT_BOTTOM_TOP;//LAYOUT_TOP_BOTTOM;//LAYOUT_BOTTOM_TOP;




    public DynamicSwatchPaneImpl() {
        init();
    }
    public DynamicSwatchPaneImpl(Color[][] colors) {
        this.setColors(colors);
        init();
        //this.setToolTipText();
    }

    /*******************************************************************************************************
     *                services
     *******************************************************************************************************/
    /**
     * found a swatch given any location(x,y) on panel
     * @param x
     * @param y
     * @return
     */
    public Swatch getSwatchByLocation(int x, int y){
        boolean contain =false;
       for (int i=0;i<numOfRow;i++){
                for (int j=0;j<numOfCulumn;j++){
                     contain = swatches[i][j].getBound().contains(x,y);
                    if(contain) return swatches[i][j];
                }
        }
        return null;
    }
    public Set <Swatch> getSwatchesIn(Rectangle r){
        Set  <Swatch> selected=new HashSet<Swatch>();
       // boolean contain =false;
       for (int i=0;i<numOfRow;i++){
                for (int j=0;j<numOfCulumn;j++){
                    Swatch aswatch = swatches[i][j];
                    Rectangle aswatchBound = aswatch.getBound();
                    if(r.contains(aswatchBound)||
                       r.intersects(aswatchBound)){
                        selected.add(aswatch);
                    }
                }
        }
        return selected;
    }

    /*******************************************************************************************************
     *                selection
     *******************************************************************************************************/

      public  void selectSwatch(Swatch swatch){
        this.clearSelection();
        this.tryAddSelection(swatch);
      }
      public  void deselectSwatch(Swatch swatch){
         if(swatch==null){
            return;
         }
         selectedSwatches.remove(swatch);
         swatch.setSelectMode(false);
      }
      public   void tryAddSelection(Swatch swatch) {
        if(swatch==null){
            //it may happen when mouse move across the gap and not
            //System.err.println("Select null swatches");
            return;
        }
        if(!this.selectedSwatches.contains(swatch) ){
            selectedSwatches.add(swatch);
            swatch.setSelectMode(true);

        }
    }
    public  void tryAddSelection(Set<Swatch> swatches) {
        if(swatches==null||swatches.size() ==0){
            //it may happen when mouse move across the gap and not
            //System.err.println("Select null swatches");
            return;
        }
        if(!this.selectedSwatches.contains(swatches) ){
            selectedSwatches.addAll(swatches);
            for (Iterator iterator = swatches.iterator(); iterator.hasNext();) {
                Swatch swatch = (Swatch) iterator.next();
                 swatch.setSelectMode(true);

            }

        }
    }

    /**
     * equal to select all
     */
    public  void clearSelection(){
         for (Iterator iterator = selectedSwatches.iterator(); iterator.hasNext();) {
             Swatch swatch = (Swatch) iterator.next();
             if(swatch!=null)
             swatch.setSelectMode(false);
         }
          selectedSwatches.clear();
   }

    /*******************************************************************************************************
     *                getter setter
     *******************************************************************************************************/
    public Swatch getSwatchByIndex(int row, int col){
        return swatches[row][col];
    }
    /**
     *
     * @param colors    
     */
    public void setColors(Color[][] colors) {
        this.numOfRow = colors.length ;
        this.numOfCulumn= colors[0].length ;
        this.colors =colors;
        this.resetSwatch();
        repaint();
    }


    public void setColors(Color[] colors) {
        this.numOfRow = 1 ;
        this.numOfCulumn= colors.length ;
        this.colors=new Color[1][colors.length ];
        this.colors[0]=colors;
        this.resetSwatch();
        repaint();
    }
     /**
     *
     * @param index     column index
     * @return
     */
    public Color[] getColumnColors(int index){
        int numOfColumn = colors[0].length;
        if(index>=numOfColumn){
            assert(false );
        }
        int numOfRow = colors.length;
        Color[] columnColors=new Color[numOfRow] ;
        for (int i=0;i<numOfRow ;i++){
            int row=i;
            Color[] rowcolors=colors[row];
            columnColors[row]=rowcolors[index];
        }
        return columnColors;

    }
    public Color[] getRowColors(int index){
       return this.colors[index];
    }

    public void setGap(int hgap, int vgap) {
        this.gap.setSize(hgap,vgap);
        this.resetSwatch();
        repaint();
    }

    public void setSwatchBorderColor(Color c) {
        //swatchBorderColor=c;
        this.setBackground(c);
        repaint();
    }

    public Set <Swatch> getSelectedSwatches() {
        return selectedSwatches;
    }
    /* public java.util.List getSwatches() {
    return null;  //To change body of implemented methods use File | Settings | File Templates.
    }*/
    /*******************************************************************************************************
     *                events
     *******************************************************************************************************/
    //make sure only a single mouse listener exists. It easy for management   
   /* public void addMouseMotionListener(MouseMotionListener l) {
        if(l!=null){
            if(mouseMotionListener!=null){
                super.removeMouseMotionListener(this.mouseMotionListener );
            }
            super.addMouseMotionListener(l);
            this.mouseMotionListener=l;
        }
    }
     public void addMouseListener(MouseListener l) {
        if(l!=null){
            if(mouseListener!=null){
                super.removeMouseListener(this.mouseListener );
            }
            super.addMouseListener(l);
            this.mouseListener=l;
        }
    }*/
    /*******************************************************************************************************
     *                support
     *******************************************************************************************************/


    private void init(){
        this.addComponentListener(new ComponentAdapter(){//reset boundary of swatches
            public void componentResized(ComponentEvent e) {
            	//System.out.print("componentResized()");
            	resetSwatch();
            	 
            }
        });
        MouseMotionAdapter mouseListener = new MouseMotionAdapter(){
                    public void mouseMoved(MouseEvent e) {
                        Swatch swatch = getSwatchByLocation(e.getX(),e.getY() );
                        if(swatch!=null)
                        DynamicSwatchPaneImpl.this.setToolTipText(swatch.description );
                    }


                };
        //this.addMouseMotionListener(mouseListener);
    }

    private void resetSwatch(){

        Dimension swatchSize=this.getSwatchSize();
        swatches=new Swatch[numOfRow][numOfCulumn];
        for (int i=0;i<numOfRow;i++){
                for (int j=0;j<numOfCulumn;j++){
                    Color color = this.getColor(i,j);
                    Rectangle bound = this.computeBound(swatchSize,i,j);
                    Swatch swatch = this.createSwatch(color,bound);
                    swatch.index.setLocation(i,j);
                    swatch.description ="Swatch ["+(i+1)+","+(j+1)+"]";
                    swatches[i][j]=swatch;
                    //this.swatches.add(swatch);
                }
            }
          this.repaint();   // 

    }
    protected  Swatch createSwatch(Color c,Rectangle r){
         Swatch swatch = new Swatch();
         swatch.color =c;
         swatch.setBound(r);

         return swatch;
    }
    /**
     *
     * @param swatchSize
     * @param ix     index of swatch, map to colors' index
     * @param iy
     * @return
     */
    private Rectangle computeBound(Dimension swatchSize,int ix, int iy){
        int row=0, column=iy;
        if(this.layouttype==LAYOUT_BOTTOM_TOP){
            row=numOfRow-ix-1;//1st swatch in bottom row( 1st row is on top)

        }
        else {
            row=ix;
        }

                int x;
                if ((!this.getComponentOrientation().isLeftToRight()) ) {
                    x = (numOfCulumn - column - 1) * (swatchSize.width + gap.width);
                } else {
                    x = column * (swatchSize.width + gap.width);
                }
                int y = row * (swatchSize.height + gap.height);
                return new Rectangle(x,y,swatchSize.width ,swatchSize.height );
    }

    public void paintComponent(final Graphics g) {
        Graphics g2d=g;
        Color originalColor = g2d.getColor();


        //1. paint background color (= gap color)
        g2d.setColor(getBackground());
        g.fillRect(0,0,getWidth(), getHeight());
        //g2d.fill3DRect(0,0,getWidth(), getHeight(),true);

        //2. paint swatches
         boolean hasSelection=false;//true if there is some swatch in selected mode
        for (int i=0;i<numOfRow;i++){
                for (int j=0;j<numOfCulumn;j++){
                    final Swatch swatch = swatches[i][j];
                    if(swatch.isSelectMode()){
                        hasSelection=true;
                        break;
                    }
                }
        }


        Set highlight=new HashSet();

        for (int i=0;i<numOfRow;i++){
                for (int j=0;j<numOfCulumn;j++){
                    final Swatch swatch = swatches[i][j];
                    if(swatch.highlightMode){
                        highlight.add(swatch);
                    }
                    else{
                        if(hasSelection){
                            if(swatch.isSelectMode() ){
                                swatch.paintComponent(g2d);
                            }
                            else{
                                swatch.paintComponent(g2d,Swatch.PAINT_MODE_REDUCED);
                            }
                        }
                        else
                            swatch.paintComponent(g2d);
                    }

                }
        }

        //3. paint highlight
        for (Iterator iterator = highlight.iterator(); iterator.hasNext();) {
            Swatch swatch = (Swatch) iterator.next();
            swatch.paintComponent(g2d); //todo should handle by Panel, not swatch
        }
        Debug.debug(DEBUG,"swatch loop paint");
        g2d.setColor(originalColor);
    }

    private Color getColor( int row,int column) {
        return colors[row][column];

    }
    public Dimension getSwatchSize(){
        Dimension size = this.getSize();
        int h = (size.height-gap.height*(numOfRow-1))/numOfRow;
        int w = (size.width-gap.width *(numOfCulumn-1)) /numOfCulumn;
        return new Dimension(w,h);

    }

    public int getLayouttype() {
        return layouttype;
    }

    public void setLayouttype(int layouttype) {
        this.layouttype = layouttype;
    }
    public int getNumOfRow() {
        return numOfRow;
    }

    public int getNumOfCulumn() {
        return numOfCulumn;
    }

    /*******************************************************************************************************
     *                inner class
     *******************************************************************************************************/

    public static class Swatch{
        public static final int PAINT_MODE_NORMAL=0;//NORMAL SIZE
        public static final int PAINT_MODE_REDUCED=1;   //reduced size

        Color color;//content color
        Color outlineColor=Color.orange ;

        private Rectangle bound;
        Point index=new Point(); //indexs of swatch, should mapping to colors' indexs
        String description;
        boolean style3d=false;
        boolean selectMode=false;
        boolean highlightMode=false;
        String[] highlightTexts; //paint in a rectangle, shown as tooltip
       // int paintmode=PAINT_MODE_NORMAL;

          public void paintComponent(Graphics g) {
               paintComponent(g,PAINT_MODE_NORMAL);
          }
         public void paintComponent(Graphics g,int paintmode) {
              g.setColor(color );

             Rectangle orgbound = getBound();//original bound

             Rectangle bd=new Rectangle();
             if (paintmode==PAINT_MODE_NORMAL) {
                 bd = orgbound;
             } else if(paintmode==PAINT_MODE_REDUCED) {
                 int x = orgbound.x;
                 int y = orgbound.y;
                 int width = orgbound.width/2;
                 int height = orgbound.height/2;
                 bd.setBounds(x,y,width,height);
             }
             else{
                 bd = orgbound;
             }


             if(this.isStyle3d() ){
                 g.fill3DRect( bd.x,bd.y,bd.width ,bd.height,true );
             }
             else{
                g.fillRect( bd.x,bd.y,bd.width ,bd.height );
             }

             /*if( this.selectMode){ //use reduced mode, not have to paint grid line
                 paintAsSelected((Graphics2D) g);
             }*/
             if(this.highlightMode ){
                 //g.setColor(this.outlineColor );
                 //g.drawRect( bd.x+1,bd.y+1,bd.width-2 ,bd.height-2); //+1 -2 is must, otherwise, the some border of this swatch will be hidden by other swatches

                 int x = bd.x+bd.width/2;
                 int y = bd.y+bd.height /2;
                 g.setColor(Color.orange );
                 g.fillRect(x,y,3,3);//to show location
                 g.setColor(new Color(255,255,255,100));
                 g.fillRect(x,y,80,50);
                 String[] texts = highlightTexts;
                 int tx=x+5;
                 int ty=y+15;
                 int textHt=20;
                 if(texts!=null){
                     for (int i=0;i<texts.length ;i++){
                         g.setFont(new Font("ARIAL",Font.BOLD ,16));
                         g.setColor(Color.black );
                         g.drawString(texts[i],tx,ty);
                         ty=ty+textHt;
                     }
                 }
             }
             //g.fill3DRect( bd.x,bd.y,bd.width ,bd.height,true );
             //System.out.print("swatch paintComponent:"+bd.toString() +","); //bound size is not update in time
         }



        protected void paintAsSelected(Graphics2D g2d){
            Rectangle bd = getBound();
            int x=bd.x ;
            int y=bd.y;
            int cellHeight=bd.height ;
            int cellWidth=bd.width ;
            //g2d.setColor(ColorSetting.getShifted(color, 53));
            g2d.setColor(ColorSetting.getShifted(color, 193));
            g2d.drawLine(x, y + cellHeight / 6, x + cellWidth,
                         y + cellHeight / 6);
            g2d.drawLine(x, y + cellHeight / 2, x + cellWidth,
                         y + cellHeight / 2);
            g2d.drawLine(x, y + cellHeight / 6 * 5, x + cellWidth,
                         y + cellHeight / 6 * 5);
            g2d.drawLine(x + cellWidth / 6, y, x + cellWidth / 6,
                         y + cellHeight);
            g2d.drawLine(x + cellWidth / 2, y, x + cellWidth / 2,
                         y + cellHeight);
            g2d.drawLine(x + cellWidth / 6 * 5, y, x + cellWidth / 6 * 5,
                         y + cellHeight);

        }

        public Color getColor() {
            return color;
        }

        public Point getIndex() {
            return index;
        }

        public void setBound(Rectangle bound) {
           // System.out.println(" set swatch bound:"+bound);
            this.bound = bound;
        }

        public Rectangle getBound() {
            return bound;
        }

        public boolean isStyle3d() {
            return style3d;
        }

        public void setStyle3d(boolean style3d) {
            this.style3d = style3d;
        }

        public void setSelectMode(boolean selectMode) {
            this.selectMode = selectMode;
        }

        public void setOutlineColor(Color outlineColor) {
            this.outlineColor = outlineColor;
        }

        public void setHighlightMode(boolean highlightMode) {
            this.highlightMode = highlightMode;
        }

        public String[] getHighlightTexts() {
            return highlightTexts;
        }

        public void setHighlightTexts(String[] highlightTexts) {
            this.highlightTexts = highlightTexts;
        }

        public boolean isSelectMode() {
            return selectMode;
        }


    }
    
}
